package qy.jalgotrade.strategy;

import java.time.ZonedDateTime;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.function.Consumer;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import qy.jalgotrade.bar.Bar;
import qy.jalgotrade.bar.Bars;
import qy.jalgotrade.barfeed.BaseBarFeed;
import qy.jalgotrade.barfeed.resampled.ResampledBarFeed;
import qy.jalgotrade.broker.Broker;
import qy.jalgotrade.broker.LimitOrder;
import qy.jalgotrade.broker.MarketOrder;
import qy.jalgotrade.broker.Order;
import qy.jalgotrade.broker.OrderEvent;
import qy.jalgotrade.broker.StopLimitOrder;
import qy.jalgotrade.broker.StopOrder;
import qy.jalgotrade.event.Dispatcher;
import qy.jalgotrade.event.Event;
import qy.jalgotrade.event.EventHandler;
import qy.jalgotrade.stratanalyzer.StrategyAnalyzer;
import qy.jalgotrade.strategy.position.LongPosition;
import qy.jalgotrade.strategy.position.Position;
import qy.jalgotrade.strategy.position.ShortPosition;

/**
 * <pre>
 * Base class for strategies.
 * 
 * .. note::
 *     This is a base class and should not be used directly.
 * </pre>
 * 
 * @author c-geo
 *
 */
public abstract class BaseStrategy implements EventHandler {

	public static final String LOGGER_NAME = "qy.jalgotrade.strategy";

	private BaseBarFeed __barFeed;

	private Broker __broker;

	private Set<Position> __activePositions;

	private Map<String, Position> __orderToPosition;

	private Event __barsProcessedEvent;

	private List<StrategyAnalyzer> __analyzers;

	private Map<String, StrategyAnalyzer> __namedAnalyzers;

	private List<ResampledBarFeed> __resampledBarFeeds;

	private Dispatcher __dispatcher;

	private Logger __logger;

	/**
	 * 专用日志方法是否添加 EventDateTime:
	 */
	private boolean __useEventDateTime;

	/**
	 * 
	 * @param barFeed The bar feed that will supply the bars.
	 * @param broker  The broker that will handle orders.
	 */
	public BaseStrategy(BaseBarFeed barFeed, Broker broker) {

		__barFeed = barFeed;
		__broker = broker;
		__activePositions = new HashSet<>();
		__orderToPosition = new HashMap<>();
		__barsProcessedEvent = new Event("strat-bar-proced-event");
		__analyzers = new ArrayList<>();
		__namedAnalyzers = new HashMap<>();
		__resampledBarFeeds = new ArrayList<>();
		__dispatcher = new Dispatcher();

		__broker.getOrderUpdatedEvent().subscribe(this);
		// 订阅 pyalgotrade.feed.BaseFeed 的 new value 可用事件 (getNewValuesEvent()):
		__barFeed.getNewValuesEvent().subscribe(this);

		// onStart will be called once all subjects are started.
		__dispatcher.getStartEvent().subscribe(this);
		__dispatcher.getIdleEvent().subscribe(this);

		// It is important to dispatch broker events before feed events, specially if we're backtesting.
		__dispatcher.addSubject(__broker);
		__dispatcher.addSubject(__barFeed);

		// Initialize logging.
		__logger = LoggerFactory.getLogger(LOGGER_NAME);
		__useEventDateTime = false;
	}

	/**
	 * XXX: Java requires calling super() at the entry of sub class constructor. Only used by sub
	 * classes.
	 */
	protected BaseStrategy() {

	}

	/**
	 * XXX: Java requires calling super() at the entry of sub class constructor. Only used by sub
	 * classes.
	 */
	protected void _init(BaseBarFeed barFeed, Broker broker) {

		__barFeed = barFeed;
		__broker = broker;
		__activePositions = new HashSet<>();
		__orderToPosition = new HashMap<>();
		__barsProcessedEvent = new Event("strat-bar-proced-event");
		__analyzers = new ArrayList<>();
		__namedAnalyzers = new HashMap<>();
		__resampledBarFeeds = new ArrayList<>();
		__dispatcher = new Dispatcher();

		__broker.getOrderUpdatedEvent().subscribe(this);
		// 订阅 pyalgotrade.feed.BaseFeed 的 new value 可用事件 (getNewValuesEvent()):
		__barFeed.getNewValuesEvent().subscribe(this);

		// onStart will be called once all subjects are started.
		__dispatcher.getStartEvent().subscribe(this);
		__dispatcher.getIdleEvent().subscribe(this);

		// It is important to dispatch broker events before feed events, specially if we're backtesting.
		__dispatcher.addSubject(__broker);
		__dispatcher.addSubject(__barFeed);

		// Initialize logging.
		__logger = LoggerFactory.getLogger(LOGGER_NAME);
		__useEventDateTime = false;
	}

	@Override
	public void handle(String eventName, Map<String, Object> params) throws Exception {

		if (eventName.equals("broker-order-event")) {

			__onOrderEvent((Broker) params.get("broker"), (OrderEvent) params.get("orderEvent"));

		} else if (eventName.equals("feed-new-values-event")) {

			__onBars((ZonedDateTime) params.get("dateTime"), (Bars) params.get("values"));

		} else if (eventName.equals("dispatcher-start-event")) {

			onStart();

		} else if (eventName.equals("dispatcher-idle-event")) {

			__onIdle();

		} else {
			throw new Exception(String.format("Unknown eventName: %s", eventName));
		}
	}

	/**
	 * Only valid for testing purposes.
	 * 
	 * @param broker broker
	 */
	void _setBroker(Broker broker) {

		__broker = broker;
	}

	/**
	 * 
	 * @return __logger
	 */
	public Logger getLogger() {

		return __logger;
	}

	/**
	 * 
	 * @param useEventDateTime useEventDateTime
	 */
	public void setUseEventDateTimeInLogs(boolean useEventDateTime) {

		// XXX:
		__useEventDateTime = useEventDateTime;
	}

	public boolean getUseAdjustedValues() {

		return false;
	}

	public Set<Position> getActivePositions() {

		return __activePositions;
	}

	// XXX: qy-pyalgotrade:
	public Position getActivePositionFor(String instrument, Order.Action type) {

		for (Position e : __activePositions) {
			if (e.getInstrument().equals(instrument) && e.getEntryOrder().getAction() == type) {
				return e;
			}
		}
		return null;
	}

	public Map<String, Position> getOrderToPosition() {

		return __orderToPosition;
	}

	public Event getBarsProcessedEvent() {

		return __barsProcessedEvent;
	}

	public Dispatcher getDispatcher() {

		return __dispatcher;
	}

	public StrategyAnalyzer getNamedAnalyzer(String name) {

		return __namedAnalyzers.get(name);
	}

	/**
	 * Returns the :class:`pyalgotrade.barfeed.BaseBarFeed` that this strategy is using.
	 * 
	 * @return __barFeed
	 */
	public BaseBarFeed getFeed() {

		return __barFeed;
	}

	/**
	 * Returns the :class:`pyalgotrade.broker.Broker` used to handle order executions.
	 * 
	 * @return __broker
	 */
	public Broker getBroker() {

		return __broker;
	}

	/**
	 * Returns the :class:`datetime.datetime` for the current :class:`pyalgotrade.bar.Bars`.
	 * 
	 * @return __barFeed.getCurrentDateTime()
	 */
	public ZonedDateTime getCurrentDateTime() {

		return __barFeed.getCurrentDateTime();
	}

	public double getLastPrice(String instrument) {

		double ret = Double.NaN;
		Bar bar = getFeed().getLastBar(instrument);
		if (bar != null) {
			ret = bar.getPrice();
		}
		return ret;
	}

	public double getResult() {

		return getBroker().getEquity();
	}

	public void registerPositionOrder(Position position, Order order) {

		__activePositions.add(position);
		assert order.isActive(); // Why register an inactive order ?
		__orderToPosition.put(order.getId(), position);
	}

	public void unregisterPositionOrder(Position position, Order order) {

		__orderToPosition.remove(order.getId());
	}

	public void unregisterPosition(Position position) {

		assert !position.isOpen();
		__activePositions.remove(position);
	}

	private void __notifyAnalyzers(Consumer<StrategyAnalyzer> notifyFunc) {

		for (StrategyAnalyzer e : __analyzers) {
			notifyFunc.accept(e);
		}
	}

	public void attachAnalyzerEx(StrategyAnalyzer strategyAnalyzer) throws Exception {

		attachAnalyzerEx(strategyAnalyzer, null);
	}

	public void attachAnalyzerEx(StrategyAnalyzer strategyAnalyzer, String name) throws Exception {

		if (!__analyzers.contains(strategyAnalyzer)) {
			if (name != null) {
				if (__namedAnalyzers.containsKey(name)) {
					throw new Exception(String.format("A different analyzer named \"%s\" was already attached", name));
				}
				__namedAnalyzers.put(name, strategyAnalyzer);
			}

			strategyAnalyzer.beforeAttach(this);
			__analyzers.add(strategyAnalyzer);
			strategyAnalyzer.attached(this);
		}
	}

	/**
	 * Adds a :class:`pyalgotrade.stratanalyzer.StrategyAnalyzer`.
	 * 
	 * @param strategyAnalyzer strategyAnalyzer
	 * @throws Exception Exception
	 */
	public void attachAnalyzer(StrategyAnalyzer strategyAnalyzer) throws Exception {

		attachAnalyzerEx(strategyAnalyzer);
	}

	/**
	 * <pre>
	 * Builds a resampled barfeed that groups bars by a certain frequency.
	 * 
	 * 基于当前 BarFeed 构建 ResampledBarFeed (保存于 self.__resampledBarFeeds 中), 时间周期为 frequency, 
	 * callback 函数参数为 bars: Bars (同 self.onBars()), 在新的 bars 可用时被调用, 返回新构建的
	 * ResampledBarFeed.
	 * </pre>
	 * 
	 * @param frequency The grouping frequency in seconds. Must be > 0.
	 * @param callback  A function similar to onBars that will be called when new bars are available.
	 * @return `pyalgotrade.barfeed.BaseBarFeed`.
	 * @throws Exception Exception
	 */
	public ResampledBarFeed resampleBarFeed(Bar.Frequency frequency, Consumer<Bars> callback) throws Exception {

		ResampledBarFeed ret = new ResampledBarFeed(getFeed(), frequency);
		ret.getNewValuesEvent().subscribe(new EventHandler() {

			Consumer<Bars> c = callback;

			@Override
			public void handle(String eventName, Map<String, Object> params) throws Exception {

				Bars bars = (Bars) params.get("values");
				c.accept(bars);
			}
		});
		getDispatcher().addSubject(ret);
		__resampledBarFeeds.add(ret);
		return ret;
	}

	/**
	 * Submits a market order.
	 * 
	 * @param instrument Instrument identifier.
	 * @param quantity   The amount of shares. Positive means buy, negative means sell.
	 * @return The :class:`pyalgotrade.broker.MarketOrder` submitted.
	 * @throws Exception Exception
	 */
	public MarketOrder marketOrder(String instrument, double quantity) throws Exception {

		return marketOrder(instrument, quantity, false, false, false);
	}

	/**
	 * Submits a market order.
	 * 
	 * @param instrument Instrument identifier.
	 * @param quantity   The amount of shares. Positive means buy, negative means sell.
	 * @param onClose    True if the order should be filled as close to the closing price as possible
	 *                   (Market-On-Close order). Default is False.
	 * @return The :class:`pyalgotrade.broker.MarketOrder` submitted.
	 * @throws Exception Exception
	 */
	public MarketOrder marketOrder(String instrument, double quantity, boolean onClose) throws Exception {

		return marketOrder(instrument, quantity, onClose, false, false);
	}

	/**
	 * Submits a market order.
	 * 
	 * @param instrument       Instrument identifier.
	 * @param quantity         The amount of shares. Positive means buy, negative means sell.
	 * @param onClose          True if the order should be filled as close to the closing price as
	 *                         possible (Market-On-Close order). Default is False.
	 * @param goodTillCanceled True if the order is good till canceled. If False then the order gets
	 *                         automatically canceled when the session closes.
	 * @return The :class:`pyalgotrade.broker.MarketOrder` submitted.
	 * @throws Exception Exception
	 */
	public MarketOrder marketOrder(String instrument, double quantity, boolean onClose, boolean goodTillCanceled)
	        throws Exception {

		return marketOrder(instrument, quantity, onClose, goodTillCanceled, false);
	}

	/**
	 * Submits a market order.
	 * 
	 * @param instrument       Instrument identifier.
	 * @param quantity         The amount of shares. Positive means buy, negative means sell.
	 * @param onClose          True if the order should be filled as close to the closing price as
	 *                         possible (Market-On-Close order). Default is False.
	 * @param goodTillCanceled True if the order is good till canceled. If False then the order gets
	 *                         automatically canceled when the session closes.
	 * @param allOrNone        True if the order should be completely filled or not at all.
	 * @return The :class:`pyalgotrade.broker.MarketOrder` submitted.
	 * @throws Exception Exception
	 */
	public MarketOrder marketOrder(String instrument, double quantity, boolean onClose, boolean goodTillCanceled,
	        boolean allOrNone) throws Exception {

		MarketOrder ret = null;
		if (quantity > 0) {
			ret = getBroker().createMarketOrder(Order.Action.BUY, instrument, quantity, onClose);
		} else if (quantity < 0) {
			ret = getBroker().createMarketOrder(Order.Action.SELL, instrument, -quantity, onClose);
		}
		if (ret != null) {
			ret.setGoodTillCanceled(goodTillCanceled);
			ret.setAllOrNone(allOrNone);
			getBroker().submitOrder(ret);
		}
		return ret;
	}

	/**
	 * Submits a limit order.
	 * 
	 * @param instrument Instrument identifier.
	 * @param limitPrice Limit price.
	 * @param quantity   The amount of shares. Positive means buy, negative means sell.
	 * @return The :class:`pyalgotrade.broker.LimitOrder` submitted.
	 * @throws Exception Exception
	 */
	public LimitOrder limitOrder(String instrument, double limitPrice, double quantity) throws Exception {

		return limitOrder(instrument, limitPrice, quantity, false, false);
	}

	/**
	 * Submits a limit order.
	 * 
	 * @param instrument       Instrument identifier.
	 * @param limitPrice       Limit price.
	 * @param quantity         The amount of shares. Positive means buy, negative means sell.
	 * @param goodTillCanceled True if the order is good till canceled. If False then the order gets
	 *                         automatically canceled when the session closes.
	 * @return The :class:`pyalgotrade.broker.LimitOrder` submitted.
	 * @throws Exception Exception
	 */
	public LimitOrder limitOrder(String instrument, double limitPrice, double quantity, boolean goodTillCanceled)
	        throws Exception {

		return limitOrder(instrument, limitPrice, quantity, goodTillCanceled, false);
	}

	/**
	 * Submits a limit order.
	 * 
	 * @param instrument       Instrument identifier.
	 * @param limitPrice       Limit price.
	 * @param quantity         The amount of shares. Positive means buy, negative means sell.
	 * @param goodTillCanceled True if the order is good till canceled. If False then the order gets
	 *                         automatically canceled when the session closes.
	 * @param allOrNone        True if the order should be completely filled or not at all.
	 * @return The :class:`pyalgotrade.broker.LimitOrder` submitted.
	 * @throws Exception Exception
	 */
	public LimitOrder limitOrder(String instrument, double limitPrice, double quantity, boolean goodTillCanceled,
	        boolean allOrNone) throws Exception {

		LimitOrder ret = null;
		if (quantity > 0) {
			ret = getBroker().createLimitOrder(Order.Action.BUY, instrument, limitPrice, quantity);
		} else if (quantity < 0) {
			ret = getBroker().createLimitOrder(Order.Action.SELL, instrument, limitPrice, -quantity);
		}
		if (ret != null) {
			ret.setGoodTillCanceled(goodTillCanceled);
			ret.setAllOrNone(allOrNone);
			getBroker().submitOrder(ret);
		}
		return ret;
	}

	/**
	 * Submits a stop order.
	 * 
	 * @param instrument Instrument identifier.
	 * @param stopPrice  Stop price.
	 * @param quantity   The amount of shares. Positive means buy, negative means sell.
	 * @return The :class:`pyalgotrade.broker.StopOrder` submitted.
	 * @throws Exception Exception
	 */
	public StopOrder stopOrder(String instrument, double stopPrice, double quantity) throws Exception {

		return stopOrder(instrument, stopPrice, quantity, false, false);
	}

	/**
	 * Submits a stop order.
	 * 
	 * @param instrument       Instrument identifier.
	 * @param stopPrice        Stop price.
	 * @param quantity         The amount of shares. Positive means buy, negative means sell.
	 * @param goodTillCanceled True if the order is good till canceled. If False then the order gets
	 *                         automatically canceled when the session closes.
	 * @return The :class:`pyalgotrade.broker.StopOrder` submitted.
	 * @throws Exception Exception
	 */
	public StopOrder stopOrder(String instrument, double stopPrice, double quantity, boolean goodTillCanceled)
	        throws Exception {

		return stopOrder(instrument, stopPrice, quantity, goodTillCanceled, false);
	}

	/**
	 * Submits a stop order.
	 * 
	 * @param instrument       Instrument identifier.
	 * @param stopPrice        Stop price.
	 * @param quantity         The amount of shares. Positive means buy, negative means sell.
	 * @param goodTillCanceled True if the order is good till canceled. If False then the order gets
	 *                         automatically canceled when the session closes.
	 * @param allOrNone        True if the order should be completely filled or not at all.
	 * @return The :class:`pyalgotrade.broker.StopOrder` submitted.
	 * @throws Exception Exception
	 */
	public StopOrder stopOrder(String instrument, double stopPrice, double quantity, boolean goodTillCanceled,
	        boolean allOrNone) throws Exception {

		StopOrder ret = null;
		if (quantity > 0) {
			ret = getBroker().createStopOrder(Order.Action.BUY, instrument, stopPrice, quantity);
		} else if (quantity < 0) {
			ret = getBroker().createStopOrder(Order.Action.SELL, instrument, stopPrice, -quantity);
		}
		if (ret != null) {
			ret.setGoodTillCanceled(goodTillCanceled);
			ret.setAllOrNone(allOrNone);
			getBroker().submitOrder(ret);
		}
		return ret;
	}

	/**
	 * Submits a stop limit order.
	 * 
	 * @param instrument Instrument identifier.
	 * @param stopPrice  Stop price.
	 * @param limitPrice Limit price.
	 * @param quantity   The amount of shares. Positive means buy, negative means sell.
	 * @return The :class:`pyalgotrade.broker.StopLimitOrder` submitted.
	 * @throws Exception Exception
	 */
	public StopLimitOrder stopLimitOrder(String instrument, double stopPrice, double limitPrice, double quantity)
	        throws Exception {

		return stopLimitOrder(instrument, stopPrice, limitPrice, quantity, false, false);
	}

	/**
	 * Submits a stop limit order.
	 * 
	 * @param instrument       Instrument identifier.
	 * @param stopPrice        Stop price.
	 * @param limitPrice       Limit price.
	 * @param quantity         The amount of shares. Positive means buy, negative means sell.
	 * @param goodTillCanceled True if the order is good till canceled. If False then the order gets
	 *                         automatically canceled when the session closes.
	 * @return The :class:`pyalgotrade.broker.StopLimitOrder` submitted.
	 * @throws Exception Exception
	 */
	public StopLimitOrder stopLimitOrder(String instrument, double stopPrice, double limitPrice, double quantity,
	        boolean goodTillCanceled) throws Exception {

		return stopLimitOrder(instrument, stopPrice, limitPrice, quantity, goodTillCanceled, false);
	}

	/**
	 * Submits a stop limit order.
	 * 
	 * @param instrument       Instrument identifier.
	 * @param stopPrice        Stop price.
	 * @param limitPrice       Limit price.
	 * @param quantity         The amount of shares. Positive means buy, negative means sell.
	 * @param goodTillCanceled True if the order is good till canceled. If False then the order gets
	 *                         automatically canceled when the session closes.
	 * @param allOrNone        True if the order should be completely filled or not at all.
	 * @return The :class:`pyalgotrade.broker.StopLimitOrder` submitted.
	 * @throws Exception Exception
	 */
	public StopLimitOrder stopLimitOrder(String instrument, double stopPrice, double limitPrice, double quantity,
	        boolean goodTillCanceled, boolean allOrNone) throws Exception {

		StopLimitOrder ret = null;
		if (quantity > 0) {
			ret = getBroker().createStopLimitOrder(Order.Action.BUY, instrument, stopPrice, limitPrice, quantity);
		} else if (quantity < 0) {
			ret = getBroker().createStopLimitOrder(Order.Action.SELL, instrument, stopPrice, limitPrice, -quantity);
		}
		if (ret != null) {
			ret.setGoodTillCanceled(goodTillCanceled);
			ret.setAllOrNone(allOrNone);
			getBroker().submitOrder(ret);
		}
		return ret;
	}

	/**
	 * Generates a buy :class:`pyalgotrade.broker.MarketOrder` to enter a long position.
	 * 
	 * @param instrument Instrument identifier.
	 * @param quantity   Entry order quantity.
	 * @return The :class:`pyalgotrade.strategy.position.Position` entered.
	 * @throws Exception Exception
	 */
	public LongPosition enterLong(String instrument, double quantity) throws Exception {

		return enterLong(instrument, quantity, false, false);
	}

	/**
	 * Generates a buy :class:`pyalgotrade.broker.MarketOrder` to enter a long position.
	 * 
	 * @param instrument       Instrument identifier.
	 * @param quantity         Entry order quantity.
	 * @param goodTillCanceled True if the entry order is good till canceled. If False then the order
	 *                         gets automatically canceled when the session closes.
	 * @return The :class:`pyalgotrade.strategy.position.Position` entered.
	 * @throws Exception Exception
	 */
	public LongPosition enterLong(String instrument, double quantity, boolean goodTillCanceled) throws Exception {

		return enterLong(instrument, quantity, goodTillCanceled, false);
	}

	/**
	 * Generates a buy :class:`pyalgotrade.broker.MarketOrder` to enter a long position.
	 * 
	 * @param instrument       Instrument identifier.
	 * @param quantity         Entry order quantity.
	 * @param goodTillCanceled True if the entry order is good till canceled. If False then the order
	 *                         gets automatically canceled when the session closes.
	 * @param allOrNone        True if the orders should be completely filled or not at all.
	 * @return The :class:`pyalgotrade.strategy.position.Position` entered.
	 * @throws Exception Exception
	 */
	public LongPosition enterLong(String instrument, double quantity, boolean goodTillCanceled, boolean allOrNone)
	        throws Exception {

		return new LongPosition(this, instrument, Double.NaN, Double.NaN, quantity, goodTillCanceled, allOrNone);
	}

	/**
	 * Generates a sell short :class:`pyalgotrade.broker.MarketOrder` to enter a short position.
	 * 
	 * @param instrument Instrument identifier.
	 * @param quantity   Entry order quantity.
	 * @return The :class:`pyalgotrade.strategy.position.Position` entered.
	 * @throws Exception Exception
	 */
	public ShortPosition enterShort(String instrument, double quantity) throws Exception {

		return enterShort(instrument, quantity, false, false);
	}

	/**
	 * Generates a sell short :class:`pyalgotrade.broker.MarketOrder` to enter a short position.
	 * 
	 * @param instrument       Instrument identifier.
	 * @param quantity         Entry order quantity.
	 * @param goodTillCanceled True if the entry order is good till canceled. If False then the order
	 *                         gets automatically canceled when the session closes.
	 * @return The :class:`pyalgotrade.strategy.position.Position` entered.
	 * @throws Exception Exception
	 */
	public ShortPosition enterShort(String instrument, double quantity, boolean goodTillCanceled) throws Exception {

		return enterShort(instrument, quantity, goodTillCanceled, false);
	}

	/**
	 * Generates a sell short :class:`pyalgotrade.broker.MarketOrder` to enter a short position.
	 * 
	 * @param instrument       Instrument identifier.
	 * @param quantity         Entry order quantity.
	 * @param goodTillCanceled True if the entry order is good till canceled. If False then the order
	 *                         gets automatically canceled when the session closes.
	 * @param allOrNone        True if the orders should be completely filled or not at all.
	 * @return The :class:`pyalgotrade.strategy.position.Position` entered.
	 * @throws Exception Exception
	 */
	public ShortPosition enterShort(String instrument, double quantity, boolean goodTillCanceled, boolean allOrNone)
	        throws Exception {

		return new ShortPosition(this, instrument, Double.NaN, Double.NaN, quantity, goodTillCanceled, allOrNone);
	}

	/**
	 * Generates a buy :class:`pyalgotrade.broker.LimitOrder` to enter a long position.
	 * 
	 * @param instrument Instrument identifier.
	 * @param limitPrice Limit price.
	 * @param quantity   Entry order quantity.
	 * @return The :class:`pyalgotrade.strategy.position.Position` entered.
	 * @throws Exception Exception
	 */
	public LongPosition enterLongLimit(String instrument, double limitPrice, double quantity) throws Exception {

		return enterLongLimit(instrument, limitPrice, quantity, false, false);
	}

	/**
	 * Generates a buy :class:`pyalgotrade.broker.LimitOrder` to enter a long position.
	 * 
	 * @param instrument       Instrument identifier.
	 * @param limitPrice       Limit price.
	 * @param quantity         Entry order quantity.
	 * @param goodTillCanceled True if the entry order is good till canceled. If False then the order
	 *                         gets automatically canceled when the session closes.
	 * @return The :class:`pyalgotrade.strategy.position.Position` entered.
	 * @throws Exception Exception
	 */
	public LongPosition enterLongLimit(String instrument, double limitPrice, double quantity, boolean goodTillCanceled)
	        throws Exception {

		return enterLongLimit(instrument, limitPrice, quantity, goodTillCanceled, false);
	}

	/**
	 * Generates a buy :class:`pyalgotrade.broker.LimitOrder` to enter a long position.
	 * 
	 * @param instrument       Instrument identifier.
	 * @param limitPrice       Limit price.
	 * @param quantity         Entry order quantity.
	 * @param goodTillCanceled True if the entry order is good till canceled. If False then the order
	 *                         gets automatically canceled when the session closes.
	 * @param allOrNone        True if the orders should be completely filled or not at all.
	 * @return The :class:`pyalgotrade.strategy.position.Position` entered.
	 * @throws Exception Exception
	 */
	public LongPosition enterLongLimit(String instrument, double limitPrice, double quantity, boolean goodTillCanceled,
	        boolean allOrNone) throws Exception {

		return new LongPosition(this, instrument, Double.NaN, limitPrice, quantity, goodTillCanceled, allOrNone);
	}

	/**
	 * Generates a sell short :class:`pyalgotrade.broker.LimitOrder` to enter a short position.
	 * 
	 * @param instrument Instrument identifier.
	 * @param limitPrice Limit price.
	 * @param quantity   Entry order quantity.
	 * @return The :class:`pyalgotrade.strategy.position.Position` entered.
	 * @throws Exception Exception
	 */
	public ShortPosition enterShortLimit(String instrument, double limitPrice, double quantity) throws Exception {

		return enterShortLimit(instrument, limitPrice, quantity, false, false);
	}

	/**
	 * Generates a sell short :class:`pyalgotrade.broker.LimitOrder` to enter a short position.
	 * 
	 * @param instrument       Instrument identifier.
	 * @param limitPrice       Limit price.
	 * @param quantity         Entry order quantity.
	 * @param goodTillCanceled True if the entry order is good till canceled. If False then the order
	 *                         gets automatically canceled when the session closes.
	 * @return The :class:`pyalgotrade.strategy.position.Position` entered.
	 * @throws Exception Exception
	 */
	public ShortPosition enterShortLimit(String instrument, double limitPrice, double quantity,
	        boolean goodTillCanceled) throws Exception {

		return enterShortLimit(instrument, limitPrice, quantity, goodTillCanceled, false);
	}

	/**
	 * Generates a sell short :class:`pyalgotrade.broker.LimitOrder` to enter a short position.
	 * 
	 * @param instrument       Instrument identifier.
	 * @param limitPrice       Limit price.
	 * @param quantity         Entry order quantity.
	 * @param goodTillCanceled True if the entry order is good till canceled. If False then the order
	 *                         gets automatically canceled when the session closes.
	 * @param allOrNone        True if the orders should be completely filled or not at all.
	 * @return The :class:`pyalgotrade.strategy.position.Position` entered.
	 * @throws Exception Exception
	 */
	public ShortPosition enterShortLimit(String instrument, double limitPrice, double quantity,
	        boolean goodTillCanceled, boolean allOrNone) throws Exception {

		return new ShortPosition(this, instrument, Double.NaN, limitPrice, quantity, goodTillCanceled, allOrNone);
	}

	/**
	 * Generates a buy :class:`pyalgotrade.broker.StopOrder` to enter a long position.
	 * 
	 * @param instrument Instrument identifier.
	 * @param stopPrice  Stop price.
	 * @param quantity   Entry order quantity.
	 * @return The :class:`pyalgotrade.strategy.position.Position` entered.
	 * @throws Exception Exception
	 */
	public LongPosition enterLongStop(String instrument, double stopPrice, double quantity) throws Exception {

		return enterLongStop(instrument, stopPrice, quantity, false, false);
	}

	/**
	 * Generates a buy :class:`pyalgotrade.broker.StopOrder` to enter a long position.
	 * 
	 * @param instrument       Instrument identifier.
	 * @param stopPrice        Stop price.
	 * @param quantity         Entry order quantity.
	 * @param goodTillCanceled True if the entry order is good till canceled. If False then the order
	 *                         gets automatically canceled when the session closes.
	 * @return The :class:`pyalgotrade.strategy.position.Position` entered.
	 * @throws Exception Exception
	 */
	public LongPosition enterLongStop(String instrument, double stopPrice, double quantity, boolean goodTillCanceled)
	        throws Exception {

		return enterLongStop(instrument, stopPrice, quantity, goodTillCanceled, false);
	}

	/**
	 * Generates a buy :class:`pyalgotrade.broker.StopOrder` to enter a long position.
	 * 
	 * @param instrument       Instrument identifier.
	 * @param stopPrice        Stop price.
	 * @param quantity         Entry order quantity.
	 * @param goodTillCanceled True if the entry order is good till canceled. If False then the order
	 *                         gets automatically canceled when the session closes.
	 * @param allOrNone        True if the orders should be completely filled or not at all.
	 * @return The :class:`pyalgotrade.strategy.position.Position` entered.
	 * @throws Exception Exception
	 */
	public LongPosition enterLongStop(String instrument, double stopPrice, double quantity, boolean goodTillCanceled,
	        boolean allOrNone) throws Exception {

		return new LongPosition(this, instrument, stopPrice, Double.NaN, quantity, goodTillCanceled, allOrNone);
	}

	/**
	 * Generates a sell short :class:`pyalgotrade.broker.StopOrder` to enter a short position.
	 * 
	 * @param instrument Instrument identifier.
	 * @param stopPrice  Stop price.
	 * @param quantity   Entry order quantity.
	 * @return The :class:`pyalgotrade.strategy.position.Position` entered.
	 * @throws Exception Exception
	 */
	public ShortPosition enterShortStop(String instrument, double stopPrice, double quantity) throws Exception {

		return enterShortStop(instrument, stopPrice, quantity, false, false);
	}

	/**
	 * Generates a sell short :class:`pyalgotrade.broker.StopOrder` to enter a short position.
	 * 
	 * @param instrument       Instrument identifier.
	 * @param stopPrice        Stop price.
	 * @param quantity         Entry order quantity.
	 * @param goodTillCanceled True if the entry order is good till canceled. If False then the order
	 *                         gets automatically canceled when the session closes.
	 * @return The :class:`pyalgotrade.strategy.position.Position` entered.
	 * @throws Exception Exception
	 */
	public ShortPosition enterShortStop(String instrument, double stopPrice, double quantity, boolean goodTillCanceled)
	        throws Exception {

		return enterShortStop(instrument, stopPrice, quantity, goodTillCanceled, false);
	}

	/**
	 * Generates a sell short :class:`pyalgotrade.broker.StopOrder` to enter a short position.
	 * 
	 * @param instrument       Instrument identifier.
	 * @param stopPrice        Stop price.
	 * @param quantity         Entry order quantity.
	 * @param goodTillCanceled True if the entry order is good till canceled. If False then the order
	 *                         gets automatically canceled when the session closes.
	 * @param allOrNone        True if the orders should be completely filled or not at all.
	 * @return The :class:`pyalgotrade.strategy.position.Position` entered.
	 * @throws Exception Exception
	 */
	public ShortPosition enterShortStop(String instrument, double stopPrice, double quantity, boolean goodTillCanceled,
	        boolean allOrNone) throws Exception {

		return new ShortPosition(this, instrument, stopPrice, Double.NaN, quantity, goodTillCanceled, allOrNone);
	}

	/**
	 * Generates a buy :class:`pyalgotrade.broker.StopLimitOrder` order to enter a long position.
	 * 
	 * @param instrument Instrument identifier.
	 * @param stopPrice  Stop price.
	 * @param limitPrice Limit price.
	 * @param quantity   Entry order quantity.
	 * @return The :class:`pyalgotrade.strategy.position.Position` entered.
	 * @throws Exception Exception
	 */
	public LongPosition enterLongStopLimit(String instrument, double stopPrice, double limitPrice, double quantity)
	        throws Exception {

		return enterLongStopLimit(instrument, stopPrice, limitPrice, quantity, false, false);
	}

	/**
	 * Generates a buy :class:`pyalgotrade.broker.StopLimitOrder` order to enter a long position.
	 * 
	 * @param instrument       Instrument identifier.
	 * @param stopPrice        Stop price.
	 * @param limitPrice       Limit price.
	 * @param quantity         Entry order quantity.
	 * @param goodTillCanceled True if the entry order is good till canceled. If False then the order
	 *                         gets automatically canceled when the session closes.
	 * @return The :class:`pyalgotrade.strategy.position.Position` entered.
	 * @throws Exception Exception
	 */
	public LongPosition enterLongStopLimit(String instrument, double stopPrice, double limitPrice, double quantity,
	        boolean goodTillCanceled) throws Exception {

		return enterLongStopLimit(instrument, stopPrice, limitPrice, quantity, goodTillCanceled, false);
	}

	/**
	 * Generates a buy :class:`pyalgotrade.broker.StopLimitOrder` order to enter a long position.
	 * 
	 * @param instrument       Instrument identifier.
	 * @param stopPrice        Stop price.
	 * @param limitPrice       Limit price.
	 * @param quantity         Entry order quantity.
	 * @param goodTillCanceled True if the entry order is good till canceled. If False then the order
	 *                         gets automatically canceled when the session closes.
	 * @param allOrNone        True if the orders should be completely filled or not at all.
	 * @return The :class:`pyalgotrade.strategy.position.Position` entered.
	 * @throws Exception Exception
	 */
	public LongPosition enterLongStopLimit(String instrument, double stopPrice, double limitPrice, double quantity,
	        boolean goodTillCanceled, boolean allOrNone) throws Exception {

		return new LongPosition(this, instrument, stopPrice, limitPrice, quantity, goodTillCanceled, allOrNone);
	}

	/**
	 * Generates a sell short :class:`pyalgotrade.broker.StopLimitOrder` order to enter a short
	 * position.
	 * 
	 * @param instrument Instrument identifier.
	 * @param stopPrice  The Stop price.
	 * @param limitPrice Limit price.
	 * @param quantity   Entry order quantity.
	 * @return The :class:`pyalgotrade.strategy.position.Position` entered.
	 * @throws Exception Exception
	 */
	public ShortPosition enterShortStopLimit(String instrument, double stopPrice, double limitPrice, double quantity)
	        throws Exception {

		return enterShortStopLimit(instrument, stopPrice, limitPrice, quantity, false, false);
	}

	/**
	 * Generates a sell short :class:`pyalgotrade.broker.StopLimitOrder` order to enter a short
	 * position.
	 * 
	 * @param instrument       Instrument identifier.
	 * @param stopPrice        The Stop price.
	 * @param limitPrice       Limit price.
	 * @param quantity         Entry order quantity.
	 * @param goodTillCanceled True if the entry order is good till canceled. If False then the order
	 *                         gets automatically canceled when the session closes.
	 * @return The :class:`pyalgotrade.strategy.position.Position` entered.
	 * @throws Exception Exception
	 */
	public ShortPosition enterShortStopLimit(String instrument, double stopPrice, double limitPrice, double quantity,
	        boolean goodTillCanceled) throws Exception {

		return enterShortStopLimit(instrument, stopPrice, limitPrice, quantity, goodTillCanceled, false);
	}

	/**
	 * Generates a sell short :class:`pyalgotrade.broker.StopLimitOrder` order to enter a short
	 * position.
	 * 
	 * @param instrument       Instrument identifier.
	 * @param stopPrice        The Stop price.
	 * @param limitPrice       Limit price.
	 * @param quantity         Entry order quantity.
	 * @param goodTillCanceled True if the entry order is good till canceled. If False then the order
	 *                         gets automatically canceled when the session closes.
	 * @param allOrNone        True if the orders should be completely filled or not at all.
	 * @return The :class:`pyalgotrade.strategy.position.Position` entered.
	 * @throws Exception Exception
	 */
	public ShortPosition enterShortStopLimit(String instrument, double stopPrice, double limitPrice, double quantity,
	        boolean goodTillCanceled, boolean allOrNone) throws Exception {

		return new ShortPosition(this, instrument, stopPrice, limitPrice, quantity, goodTillCanceled, allOrNone);
	}

	/**
	 * Override (optional) to get notified when the order submitted to enter a position was filled. The
	 * default implementation is empty.
	 * 
	 * @param position A position returned by any of the enterLongXXX or enterShortXXX methods.
	 */
	public void onEnterOk(Position position) {

	}

	/**
	 * Override (optional) to get notified when the order submitted to enter a position was canceled.
	 * The default implementation is empty.
	 * 
	 * @param position A position returned by any of the enterLongXXX or enterShortXXX methods.
	 */
	public void onEnterCanceled(Position position) {

	}

	/**
	 * <pre>
	 * Override (optional) to get notified when the order submitted to exit a position was filled. The
	 * default implementation is empty.
	 * 
	 * Called when the exit order for a position was filled.
	 * </pre>
	 * 
	 * @param position A position returned by any of the enterLongXXX or enterShortXXX methods.
	 */
	public void onExitOk(Position position) {

	}

	/**
	 * <pre>
	 * Override (optional) to get notified when the order submitted to exit a position was canceled. The
	 * default implementation is empty.
	 * 
	 * Called when the exit order for a position was canceled.
	 * </pre>
	 * 
	 * @param position A position returned by any of the enterLongXXX or enterShortXXX methods.
	 */
	public void onExitCanceled(Position position) {

	}

	/*
	 * Base class for strategies.
	 */

	/**
	 * Override (optional) to get notified when the strategy starts executing. The default
	 * implementation is empty.
	 */
	public void onStart() {

	}

	/**
	 * Override (optional) to get notified when the strategy finished executing. The default
	 * implementation is empty.
	 * 
	 * @param bars The last bars processed.
	 */
	public void onFinish(Bars bars) {

	}

	/**
	 * <pre>
	 * Override (optional) to get notified when there are no events.
	 * 
	 * .. note::
	 *     In a pure backtesting scenario this will not be called.
	 * </pre>
	 */
	public void onIdle() {

	}

	/**
	 * <pre>
	 * Override (**mandatory**) to get notified when new bars are available. The default implementation
	 * raises an Exception.
	 * 
	 * **This is the method to override to enter your trading logic and enter/exit positions**.
	 * </pre>
	 * 
	 * @param bars bars
	 * @throws Exception Exception
	 */
	public abstract void onBars(Bars bars) throws Exception;

	/**
	 * Override (optional) to get notified when an order gets updated.
	 * 
	 * @param order The order updated.
	 */
	public void onOrderUpdated(Order order) {

	}

	/**
	 * Force a resample check to avoid depending solely on the underlying barfeed events.
	 * @throws Exception Exception
	 */
	private void __onIdle() throws Exception {

		for (ResampledBarFeed feed : __resampledBarFeeds) {
			feed.checkNow(getCurrentDateTime());
		}

		onIdle();
	}

	private void __onOrderEvent(Broker broker, OrderEvent orderEvent) throws Exception {

		Order order = orderEvent.getOrder();
		onOrderUpdated(order);

		// Notify the position about the order event.
		Position pos = __orderToPosition.get(order.getId());
		if (pos != null) {
			// Unlink the order from the position if its not active anymore.
			if (!order.isActive()) {
				unregisterPositionOrder(pos, order);
			}
			pos.onOrderEvent(orderEvent);
		}
	}

	private void __onBars(ZonedDateTime dateTime, Bars bars) throws Exception {

		// THE ORDER HERE IS VERY IMPORTANT

		// 1: Let analyzers process bars.
		__notifyAnalyzers(sa -> sa.beforeOnBars(this, bars));

		// 2: Let the strategy process current bars and submit orders.
		onBars(bars);

		// 3: Notify that the bars were processed.
		Map<String, Object> params = new HashMap<>();
		params.put("strategy", this);
		params.put("bars", bars);
		__barsProcessedEvent.emit(__barsProcessedEvent.getName(), params);
	}

	/**
	 * Call once (**and only once**) to run the strategy.
	 * 
	 * @throws Exception Exception
	 */
	public void run() throws Exception {

		__dispatcher.run();

		if (__barFeed.getCurrentBars() != null) {
			onFinish(__barFeed.getCurrentBars());
		} else {
			throw new Exception("Feed was empty");
		}
	}

	/**
	 * Stops a running strategy.
	 */
	public void stop() {

		__dispatcher.stop();
	}

	/**
	 * Logs a message with level DEBUG on the strategy logger.
	 * 
	 * @param msg msg
	 */
	public void debug(String msg) {

		if (getLogger().isDebugEnabled()) {
			getLogger().debug(__addEventDateTimeMsg(msg));
		}
	}

	/**
	 * Logs a message with level INFO on the strategy logger.
	 * 
	 * @param msg msg
	 */
	public void info(String msg) {

		if (getLogger().isInfoEnabled()) {
			getLogger().info(__addEventDateTimeMsg(msg));
		}
	}

	/**
	 * Logs a message with level WARNING on the strategy logger.
	 * 
	 * @param msg msg
	 */
	public void warning(String msg) {

		if (getLogger().isWarnEnabled()) {
			getLogger().warn(__addEventDateTimeMsg(msg));
		}
	}

	/**
	 * Logs a message with level ERROR on the strategy logger.
	 * 
	 * @param msg msg
	 */
	public void error(String msg) {

		if (getLogger().isErrorEnabled()) {
			getLogger().error(__addEventDateTimeMsg(msg));
		}
	}

	/**
	 * Logs a message with level CRITICAL on the strategy logger.
	 * 
	 * @param msg msg
	 */
	public void critical(String msg) {

		error(msg); // TODO: adapt CRITICAL level for python logger
	}

	private String __addEventDateTimeMsg(String oriMsg) {

		if (__useEventDateTime) {
			String tmpl = "{Bar@%s} %s";
			if (getCurrentDateTime() == null) {
				return oriMsg;
			} else {
				return String.format(tmpl,
				        getCurrentDateTime().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss O")), oriMsg);
			}
		} else {
			return oriMsg;
		}
	}
}
